/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Public License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Public License, please send an email to 
 * dlr@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Public License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Reflection.Emit;
using Microsoft.Scripting.Generation;
using Microsoft.Scripting.Utils;

namespace Microsoft.Scripting.Ast {
    internal sealed partial class ScopeInfo {

        /// <summary>
        /// Represents an environment, which contains only lifted variables
        /// and is stored in an array. Independant of ScopeInfo because not all
        /// scopes have an environment.
        /// 
        /// Invariant: this class stores no compiler state and is safe to emit
        ///   under different LambdaCompilers
        /// </summary>
        private sealed class EnvironmentInfo {
            // The parent environment, if any
            private readonly EnvironmentInfo _parent;

            // A mapping of lifted variables to their indexes in the tuple
            private readonly Dictionary<Expression, int> _indexes;

            // The variables, in the order they appear in the array
            private readonly ReadOnlyCollection<Expression> _vars;

            // We store the variables in an object array 
            private readonly static Type _EnvironmentType = typeof(object[]);

            internal EnvironmentInfo(EnvironmentInfo parent, ReadOnlyCollection<Expression> vars) {
                if (parent != null) {
                    // Add the parent environment as the first "virtual" variable
                    Expression parentVar = Expression.Variable(_EnvironmentType, "$parentEnv");
                    vars = new ReadOnlyCollection<Expression>(ArrayUtils.Insert(parentVar, vars));
                }

                Dictionary<Expression, int> indexes = new Dictionary<Expression, int>(vars.Count);
                for (int i = 0; i < vars.Count; i++) {
                    indexes.Add(vars[i], i);
                }

                _vars = vars;
                _indexes = indexes;
                _parent = parent;
            }

            internal EnvironmentInfo Parent {
                get { return _parent; }
            }

            internal ReadOnlyCollection<Expression> Variables {
                get { return _vars; }
            }

            #region element access

            internal Slot GetElementSlot(Slot environment, Expression variable) {
                Debug.Assert(_indexes.ContainsKey(variable));
                Debug.Assert(environment.Type == _EnvironmentType);

                int index = _indexes[variable];
                return CastSlot.Cast(_vars[index].Type, new IndexSlot(environment, index));
            }

            internal Slot GetParentSlot(LocalSlot environment) {
                return GetElementSlot(environment, _vars[0]);
            }

            internal bool DefinesVariable(Expression variable) {
                return _indexes.ContainsKey(variable);
            }

            internal int GetVariableIndex(Expression variable) {
                return _indexes[variable];
            }

            #endregion

            #region initialization

            internal LocalSlot EmitCreate(ILGen cg, Slot parentEnv) {
                Debug.Assert((parentEnv == null) == (_parent == null));

                cg.EmitInt(_vars.Count);
                cg.Emit(OpCodes.Newarr, _EnvironmentType.GetElementType());

                // initialize value type elements to default(T)
                int i = 0;
                foreach (Expression v in _vars) {
                    if (v.Type.IsValueType) {
                        // array[i] = (object)default(T)
                        cg.Emit(OpCodes.Dup);
                        cg.EmitInt(i);
                        cg.EmitDefault(v.Type);
                        cg.Emit(OpCodes.Box, v.Type);
                        cg.Emit(OpCodes.Stelem_Ref);
                    }
                    i++;
                }

                LocalSlot env = cg.EmitSetLocal(_EnvironmentType);

                // set the parent
                if (_parent != null) {
                    GetParentSlot(env).EmitSet(cg, parentEnv);
                }

                return env;
            }

            internal static LocalSlot EmitFromClosureArgument(LambdaCompiler cg) {
                cg.ClosureSlot.EmitGet(cg.IL);
                cg.IL.Emit(OpCodes.Ldfld, typeof(Closure).GetField("Environment"));
                return cg.IL.EmitSetLocal(_EnvironmentType);
            }

            #endregion
        }

    }
}
